#include <stdio.h>
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "driver/i2s_pdm.h"
#include "esp_lcd_io_spi.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_lcd_touch_cst816s.h"
#include "lvgl.h"
#include "ui/ui.h"

/* Custom Components */
#include "lsm6dso.h"
#include "bmp280.h"

static const char *TAG = "main";

/* LCD SPI Config */
#define LCD_HOST    SPI2_HOST

#define LCD_H_RES    240
#define LCD_V_RES    280

#define PIN_NUM_MISO            1
#define PIN_NUM_MOSI            41
#define PIN_NUM_CLK             40
#define PIN_NUM_CS              39
#define PIN_NUM_DC              38
#define PIN_NUM_RST             42
#define PIN_NUM_BCKL            45
#define LCD_BK_LIGHT_ON_LEVEL   1

/* I2C Bus Config */
#define I2C_PORT                0
#define I2C_MASTER_SCL_IO       47
#define I2C_MASTER_SDA_IO       48
#define I2C_MASTER_TIMEOUT_MS   10000

/* CST816 Pin Config */
#define LCD_TOUCH_RST   42
#define LCD_TOUCH_INT   2

/* LSM6DSO Pin Config */
#define LSM6DSO_CS      14
#define LSM6DSO_INT1    13
#define LSM6DSO_INT2    21

LSM6DSO_Data_t lsm6dso_data;

BMP280_Data_t bmp280_data;

/* Hardware test settings */
//#define TEST_MEMS_MIC   1
//#define TEST_LSM6DSO    1
//#define TEST_BMP280     1
#define TEST_SCREEN     1

/* MEMS Mic I2S Config */
#ifdef TEST_MEMS_MIC
#define PDM_RX_CLK_PIN        GPIO_NUM_12
#define PDM_RX_DATA_PIN       GPIO_NUM_11
#define PDM_RX_FREQ_HZ        8000

#define EXAMPLE_SAMPLE_RATE   PDM_RX_FREQ_HZ
#define EXAMPLE_BIT_SAMPLE    16
#define REC_TIME              1
#define BYTE_RATE             (EXAMPLE_SAMPLE_RATE * (EXAMPLE_BIT_SAMPLE / 8))
#define SAMPLE_SIZE           (BYTE_RATE * REC_TIME)

int16_t i2s_readraw_buffer[SAMPLE_SIZE];
size_t bytes_read;

void record_raw(i2s_chan_handle_t rx_handle)
{
    if (i2s_channel_read(rx_handle, (char *)i2s_readraw_buffer, SAMPLE_SIZE, &bytes_read, REC_TIME*1000) == ESP_OK) {
        printf("Mic Read Successed!\n");
    } 
    else printf("Mic Read Failed!\n");
}
#endif

#ifdef TEST_SCREEN
#define EXAMPLE_LVGL_TICK_PERIOD_MS     2               /*!< LVGL tick period in ms */
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS  500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS  1
#define EXAMPLE_LVGL_TASK_STACK_SIZE    (5 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY      2

static SemaphoreHandle_t    touch_mux   = NULL;
static SemaphoreHandle_t    lvgl_mux    = NULL;

/* The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct. */
typedef struct {
    uint8_t cmd;
    uint8_t data[16];
    uint8_t databytes;
    uint8_t delay_ms;
} lcd_init_cmd_t;

DRAM_ATTR static const lcd_init_cmd_t lcd_init_cmds[] = {
        /* {cmd, { data }, data_size, delay_ms} */ 
        {0x11, {0x00}, 1, 120},
        //{0x36, {0x00}, 1, 0},
        //{0x3A, {0x06}, 1, 0},
        {0xB2, {0x0B, 0x0B, 0x00, 0x33, 0x33}, 5, 0},
        {0xB7, {0x11}, 1, 0},
        {0xBB, {0x2F}, 1, 0},
        {0xC0, {0x2C}, 1, 0},
        {0xC2, {0x01}, 1, 0},
        {0xC3, {0x0D}, 1, 0},
        {0xC4, {0x20}, 1, 0},
        {0xC6, {0x13}, 1, 0},
        {0xD0, {0xA4, 0xA1}, 2, 0},
        {0xD6, {0xA1}, 1, 0},
        {0xE0, {0xF0, 0x04, 0x07, 0x09, 0x07, 0x13, 0x25, 0x33, 0x3C, 0x34, 0x10, 0x10, 0x29, 0x32}, 14, 0},
        {0xE1, {0xF0, 0x05, 0x08, 0x0A, 0x09, 0x05, 0x25, 0x32, 0x3B, 0x3B, 0x17, 0x18, 0x2E, 0x37}, 14, 0},
        {0xE4, {0x25, 0x00, 0x00}, 3, 0},
        {0x21, {0x00}, 1, 0},
        {0x29, {0x00}, 1, 0},
        {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4, 0},
        {0x2B, {0x00, 0x14, 0x01, 0x2B}, 4, 0},
        {0x2C, {0x00}, 1, 0},
        /* End Init */
        {0x00, {0x00}, 1, 0}
};

void st7789_init(esp_lcd_panel_io_handle_t io_handle)
{
    int cmd = 0;
    while (lcd_init_cmds[cmd].cmd != 0x00) {
        esp_lcd_panel_io_tx_param(io_handle, lcd_init_cmds[cmd].cmd, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes);
        vTaskDelay(lcd_init_cmds[cmd].delay_ms / portTICK_PERIOD_MS);
        cmd++;
    }
}

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}

static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    /* Copy a buffer's content to a specific area of the display */
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

static void example_lvgl_touch_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    uint16_t touchpad_x[1]  = {0};
    uint16_t touchpad_y[1]  = {0};
    uint8_t touchpad_cnt    = 0;

    /* Read touch controller data */
    if (xSemaphoreTake(touch_mux, 0) == pdTRUE) {
        esp_lcd_touch_read_data(drv->user_data);
    }

    /* Get coordinates */
    bool touchpad_pressed = esp_lcd_touch_get_coordinates(drv->user_data, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);

    if (touchpad_pressed && touchpad_cnt > 0) {
        data->point.x = touchpad_x[0];
        data->point.y = touchpad_y[0];
        data->state = LV_INDEV_STATE_PRESSED;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

static void touch_callback(esp_lcd_touch_handle_t tp)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(touch_mux, &xHigherPriorityTaskWoken);

    if (xHigherPriorityTaskWoken) {
        portYIELD_FROM_ISR();
    }
}

static void example_increase_lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

static bool example_lvgl_lock(int timeout_ms)
{
    assert(lvgl_mux && "bsp_display_start must be called first");

    const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
    return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}

static void example_lvgl_unlock(void)
{
    assert(lvgl_mux && "bsp_display_start must be called first");
    xSemaphoreGive(lvgl_mux);
}

static void example_lvgl_port_task(void *arg)
{
    ESP_LOGI(TAG, "Starting LVGL task");
    ESP_LOGI(TAG, "Display LVGL UI");

    ui_init();

    uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
    while (1) {
        /* Lock the mutex due to the LVGL APIs are not thread-safe */
        if (example_lvgl_lock(-1)) {
            task_delay_ms = lv_timer_handler();
            /* Release the mutex */
            example_lvgl_unlock();
        }
        if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
        } else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
        }
        vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
    }
}
#endif

void app_main(void)
{
    ESP_LOGI(TAG,"Initialize I2C Bus With newer driver");
    const i2c_master_bus_config_t i2c_mst_config = {
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .i2c_port = I2C_PORT,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    i2c_master_bus_handle_t bus_handle;
    ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
    printf("I2C Bus Initialize success\n");

    ESP_LOGI(TAG,"Install BMP280 device by custom driver");
    BMP280_Object_t bmp280_object;
    bmp280_object.Addr = BMP280_I2C_ADDR_H;
    bmp280_object.SCL_Speed = 400 * 1000;
    i2c_master_dev_handle_t bmp280_handle = bmp280_init(bus_handle,&bmp280_object);
    ESP_ERROR_CHECK(bmp280_read_id(bmp280_handle));
    printf("BMP280 Test Successed\n");
    
    ESP_LOGI(TAG,"Install LSM6DSO device by custom driver");
    LSM6DSO_Object_t lsm6dso_object;
    lsm6dso_object.Addr = LSM6DSO_I2C_ADD_L;
    lsm6dso_object.Pin.LSM6DSO_CS_PIN = LSM6DSO_CS;
    lsm6dso_object.Pin.LSM6DSO_INT1_PIN = LSM6DSO_INT1;
    lsm6dso_object.Pin.LSM6DSO_INT2_PIN = LSM6DSO_INT2;
    lsm6dso_object.SCL_Speed = 400 * 1000;
    i2c_master_dev_handle_t lsm6dso_handle = NULL;
    ESP_ERROR_CHECK(lsm6dso_init(bus_handle,&lsm6dso_handle,&lsm6dso_object));
    ESP_ERROR_CHECK(lsm6dso_get_id(lsm6dso_handle));
    printf("LSM6DSO Test Successed\n");

    ESP_LOGI(TAG,"Test BM8563 connect state");
    ESP_ERROR_CHECK(i2c_master_probe(bus_handle,0x51,-1));
    printf("BM8563 Test Successed\n");

#ifdef TEST_MEMS_MIC
    /* I2S(PDM_RX_Mode) Init */
    i2s_chan_handle_t rx_handle;
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));

    /* 初始化通道为 PDM RX 模式 */
    i2s_pdm_rx_config_t pdm_rx_cfg = {
        .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(PDM_RX_FREQ_HZ),
        .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .clk = PDM_RX_CLK_PIN,
            .din = PDM_RX_DATA_PIN,
            .invert_flags = {
                .clk_inv = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));

    /* I2S 读取数据 */
    record_raw(rx_handle);

    /* 数据打印 */
    //int i;
    //for(i=0;i<2000;i++) printf("%d ", i2s_readraw_buffer[i]);
    //for(i=2000;i<4000;i++) printf("%d ", i2s_readraw_buffer[i]);
    //for(i=4000;i<6000;i++) printf("%d ", i2s_readraw_buffer[i]);
    //for(i=6000;i<8000;i++) printf("%d ", i2s_readraw_buffer[i]);
    //for(i=8000;i<10000;i++) printf("%d ", i2s_readraw_buffer[i]);
    //for(i=10000;i<12000;i++) printf("%d ", i2s_readraw_buffer[i]);
#endif

#ifdef TEST_LSM6DSO //使用库文件读取LSM6DSO数据
    
    ESP_LOGI(TAG,"Enable both acceloreter and gyroscope");
    ESP_ERROR_CHECK(lsm6dso_acc_enable(lsm6dso_handle,&lsm6dso_object));
    ESP_ERROR_CHECK(lsm6dso_acc_setpowermode(lsm6dso_handle,LSM6DSO_ACC_LOW_POWER_NORMAL_MODE,&lsm6dso_object));
    ESP_ERROR_CHECK(lsm6dso_gyro_enable(lsm6dso_handle,&lsm6dso_object));
    ESP_ERROR_CHECK(lsm6dso_gyro_setpowermode(lsm6dso_handle,LSM6DSO_GYRO_LOW_POWER_NORMAL_MODE,&lsm6dso_object));
    ESP_ERROR_CHECK(lsm6dso_timestamp_setmode(lsm6dso_handle,1,&lsm6dso_object));

    ESP_LOGI(TAG,"Start reading data");
    while(1)
    {
        //ESP_ERROR_CHECK(lsm6dso_acc_getaxes(lsm6dso_handle,&lsm6dso_data.acc_raw,&lsm6dso_data.acc,&lsm6dso_object));
        //ESP_ERROR_CHECK(lsm6dso_gyro_getaxes(lsm6dso_handle,&lsm6dso_data.gyro_raw,&lsm6dso_data.gyro,&lsm6dso_object));
        //ESP_ERROR_CHECK(lsm6dso_temp_getdata(lsm6dso_handle,&lsm6dso_data.temp));
        lsm6dso_get_data(lsm6dso_handle,&lsm6dso_data,&lsm6dso_object);
        printf("acc_x=%fmg acc_y=%fmg acc_z=%fmg ",lsm6dso_data.acc.x,lsm6dso_data.acc.y,lsm6dso_data.acc.z);
        printf("gry_x=%fmdps gry_y=%fmdps gry_z=%fmdps ",lsm6dso_data.gyro.x,lsm6dso_data.gyro.y,lsm6dso_data.gyro.z);
        printf("temp=%f time=%lu\r\n",lsm6dso_data.temp,lsm6dso_data.timestamp);
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
#endif

#ifdef TEST_BMP280 //使用库文件读取BMP280数据
    ESP_LOGI(TAG,"Read BMP280 temperature and barometric pressure data");
    for(int q=0;q<100;q++)
    {
        bmp280_read_data(bmp280_handle,&bmp280_object,&bmp280_data);
        float altitude = 44330 * (1-pow((bmp280_data.Press/(float)101325),1/5.255));
        printf("temp=%f press=%f altitude=%f\r\n",bmp280_data.Temp,bmp280_data.Press,altitude);
        vTaskDelay(250 / portTICK_PERIOD_MS); //气压读取存在问题？温度和lsm6dso值相差0-1度
    }
#endif

#ifdef TEST_SCREEN
    static lv_disp_draw_buf_t   disp_buf;   /* Contains internal graphic buffer(s) called draw buffer(s) */
    static lv_disp_drv_t        disp_drv;   /* Contains callback functions */
    
    ESP_LOGI(TAG,"Initialize SPI Bus");
    const spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = LCD_H_RES * 40 * sizeof(uint16_t)
    };
    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST,&buscfg,SPI_DMA_CH_AUTO));

    ESP_LOGI(TAG,"Configure LCD Panel IO");
    esp_lcd_panel_io_handle_t io_handle = NULL;
    const esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = PIN_NUM_DC,
        .cs_gpio_num = PIN_NUM_CS,
        .spi_mode = 0,
        .pclk_hz = 40*1000*1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .trans_queue_depth = 10,
        .on_color_trans_done = example_notify_lvgl_flush_ready,
        .user_ctx = &disp_drv,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST,&io_config,&io_handle));

    ESP_LOGI(TAG,"Configure LCD Backlight Pin");
    const gpio_config_t bk_gpio_config = {
        .mode = GPIO_MODE_OUTPUT,
        .pin_bit_mask = 1ULL << PIN_NUM_BCKL
    };
    ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

    ESP_LOGI(TAG,"Configure LCD Panel Handle");
    esp_lcd_panel_handle_t panel_handle = NULL;
    const esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = PIN_NUM_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle,&panel_config,&panel_handle));

    ESP_LOGI(TAG, "Initialize touch component");
    touch_mux = xSemaphoreCreateBinary();
    assert(touch_mux);
    esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
    tp_io_config.scl_speed_hz = 400 * 1000;
    esp_lcd_touch_config_t tp_cfg = {
        .x_max          = LCD_H_RES,
        .y_max          = LCD_V_RES,
        .rst_gpio_num   = LCD_TOUCH_RST,
        .int_gpio_num   = LCD_TOUCH_INT,
        .levels = {
            .reset      = 0,
            .interrupt  = 0,
        },
        .flags = {
            .swap_xy    = 0,
            .mirror_x   = 0,
            .mirror_y   = 0,
        },
        .interrupt_callback = touch_callback,
    };
    esp_lcd_panel_io_handle_t tp_io_handle    = NULL;
    esp_lcd_touch_handle_t    tp              = NULL;
    esp_lcd_new_panel_io_i2c(bus_handle, &tp_io_config, &tp_io_handle);
    esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &tp);

    ESP_LOGI(TAG,"Reset and Initialize LCD");//由于触摸驱动和屏幕驱动共用一个RESET引脚，需要在所有device handle初始化完成后在启动屏幕
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    st7789_init(io_handle);
    gpio_set_level(PIN_NUM_BCKL, LCD_BK_LIGHT_ON_LEVEL);
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle,true));
    
    ESP_LOGI(TAG,"Initialize LVGL Library");
    lv_init();
    lv_color_t *buf1 = heap_caps_malloc(LCD_H_RES * 40 * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1);
    lv_color_t *buf2 = heap_caps_malloc(LCD_H_RES * 40 * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf2);
    /* Initialize LVGL Draw Buffer */
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, LCD_H_RES*40);

    ESP_LOGI(TAG, "Register display driver to LVGL");
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res    = LCD_H_RES;
    disp_drv.ver_res    = LCD_V_RES;
    disp_drv.flush_cb   = example_lvgl_flush_cb;
    disp_drv.draw_buf   = &disp_buf;
    disp_drv.user_data  = panel_handle;
    disp_drv.offset_y   = 20;//屏幕特性，其行地址从20开始，所以这里要设置20的y轴偏移量
    lv_disp_t *disp     = lv_disp_drv_register(&disp_drv);

    ESP_LOGI(TAG, "Register touch driver to LVGL");
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type      = LV_INDEV_TYPE_POINTER;
    indev_drv.disp      = disp;
    indev_drv.read_cb   = example_lvgl_touch_cb;
    indev_drv.user_data = tp;
    lv_indev_drv_register(&indev_drv);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    /* Tick interface for LVGL (using esp_timer to generate 2ms periodic event) */
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback   = &example_increase_lvgl_tick,
        .name       = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

    ESP_LOGI(TAG, "Run LVGL Task");
    lvgl_mux = xSemaphoreCreateMutex();
    assert(lvgl_mux);
    xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
#endif
}
